#!/bin/bash
# SPDX-FileCopyrightText: 2015, 2020 Efabless Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
# Copyright (C) 2015, 2020 efabless Corporation. All Rights Reserved.
# filter out most options, so magic Natively sees/handles *only* -T <file>.
# for-bash\
  declare -a C ; declare -a N ; export _CE= _NE= _M0=           ;\
  for i in "$@" ; do _M0="$_M0${_M0:+ }\"${i//\"/\\\"}\""; done ;\
  while getopts "NFT:S:l:P:" o; do                      \
    : echo got "optchar $o, with optarg $OPTARG" ;\
    case "$o" in S)                               \
      C+=(-${o} "$OPTARG")                       ;\
       continue ; esac                           ;\
    case "$o" in P)                               \
      C+=(-${o} "$OPTARG")                       ;\
       continue ; esac                           ;\
    case "$o" in F|N)                             \
      C+=(-${o})                                 ;\
       continue ; esac                           ;\
    case "$o" in l)                               \
      C+=(-${o} "$OPTARG")                       ;\
       continue ; esac                           ;\
    case "$o" in T)                               \
      N+=(-${o} "$OPTARG")                       ;\
       continue ; esac                           ;\
  done                ;\
  shift $((OPTIND-1)) ;\
  for i in "${C[@]}" ; do _CE="$_CE${_CE:+ }\"${i//\"/\\\"}\""; done ;\
  for i in "${N[@]}" ; do _NE="$_NE${_NE:+ }\"${i//\"/\\\"}\""; done ;\
  exec magic -dnull -noconsole "${N[@]}" <"$0"
# for-magic:
# magicDrc: run magic-DRC in batch on a .mag file, tabulate/pareto the error counts.
#
# magicDrc [-T <techfilePath>] [-S <drcStyleName>] [-P <N> ] [-l FILE_NAME] <magFileName>
#  -T name specific techfile (def .tech extension), passed to magic itself only, overrides tech implied by magFileName
#  -S if given, changes from techfile's default drc style (perhaps "drc(fast)") to named style, for example: -S "drc(full)"
#  -l if given, enumerates EVERY individual error bbox to the FILE_NAME
#  -N if given, do Not use -dereference option of load for topcell (not available in older magics)
#  -F flatten top cell in-memory only, not saved (experimental)
#  -P do crude drc performance measurement. At top-cell, do 'drc find' <N> times and report time per call.
#   Stdout will log a pareto of error type by count regardless.
#
# <magFileName>: names a .mag file, the toplevel of the hier. to DRC/pareto
#
# Normal magic init. files are STILL sourced: ~/.magicrc and either $CWD/.magicrc or $CWD/magic_setup.
# (This would NOT happen if -rcfile magic cmd-line option were used).
#
# WARNING: Before 8.1.70, *.mag on cmd-line that was only found in cell search path set by .magicrc inits,
# would FAIL to determine the default tech-file.
#
# rb@ef 2015-06-30 author
# rb 2020-03-11 embed some library functions, to standalone from efabless-opengalaxy env, test via magic-8.2.194
#
# magic itself outputs following usage message though -rcfile doesn't appear to work (in some versions):
#   Usage:  magic [-g gPort] [-d devType] [-m monType] [-i tabletPort] [-D] [-F objFile saveFile]
#   [-T technology] [-rcfile startupFile | -norcfile][-noconsole] [-nowindow] [-wrapper] [file]
#
set Prog "magicDrc"

set argv  [eval "list $env(_M0)"] ;# orig. mix of native plus custom args, for logging all args to script

proc usage {args} {
    if {[llength $args] > 0} {
	puts "ERROR: ${::Prog}: [join $args]"
    }
    puts {usage: [ -T <techfilePath> ] [-S <drcStyleName>] [-N] [-l FILE_NAME] <magFileName>}
    puts "  -T name specific techfile, passed to magic itself only, overrides tech implied by magFileName"
    puts "  -S if given, changes from techfile's default drc style (perhaps \"drc(fast)\") to named style, for example: -S \"drc(full)\""
    puts "  -l if given, enumerates EVERY individual error bbox to the FILE_NAME"
    puts "  -N if given, do not use -dereference option of load for topcell (not available in older magics)"
    puts "  Stdout will log a pareto of error type by count regardless."
    puts ""
    puts "  Recommend to run in dir with a ./.magicrc (or ./magic_setup) to configure magic's"
    puts "  cell search path, thru addpath statements, to locate all cells."
}

# optionally hardcode library proc-s (part of site-wide extensions - always available - in context of efabless/open-galaxy)
# This is to make the script more standalone from efabless environment; but these capabilities should be native to magic.

if {[info command scratchWritable] == {}} {
    puts "${::Prog}: hardcoding library proc-s..."
# Replacement for 'cellname list exists CELLNAME', to fix ambiguity for cell "0".
# For cell "0" test for membership in 'cellname list allcells'.
#
# Instead of returning 0 for (non-existent) and cellname for exists,
# returns regular 0/1 instead for non-existent/exists.
#
# Therefore NOT direct replacement for uses of 'cellname list exists CELL'.
# Requires code changes.
proc cellnameExists {cell} {
    expr {$cell ne "0" && [cellname list exists $cell] eq $cell ||
	  $cell eq "0" && [lsearch -exact [cellname list allcells] $cell] > -1}
}

#
# scratchWritable [-cleanup] [cellname1 ...] --
#
# Turn readonly cells writable in-memory, via redirect to scratch dir.
# No cellname args: default is to process just all non-writable cells.
# Explicit cellname arguments: ARE scatchified EVEN if ALREADY writable.
# Limitation: Explicit named cell created in-mem, never saved, won't scratchify.
# If just -cleanup: default is to only do cleanup: don't scratchify
# any cells.
#
# -cleanup: Last scratch-dir, if any, and contents are deleted first.
# No restoring old filepath of cells, save after cleanup will fail.
#
# Caller strongly recommended to first do: 'select top cell; expand'
# to force whole hier. of a topcell to be loaded from disk into memory.
#
# This proc does not force expand cells. Before expanded, cells cannot be
# checked whether writable, and cannot have filepath changed.
#
# For batch DRC, for 'drc listall count', every cell in-memory must
# appear writable.  This is the work-around (caller to 1st ensure
# hier. is loaded): Reset filepath of readonly cells to a scratch dir,
# make a dummy/empty .mag in scratch dir for each cell. Change cell's
# writeable flag.
#
# Skipped cells:
# In all cases, cells are skipped if
#   'cellname filepath' matches ::scratchWritableDir (already scratchified),
# This proc does NOT try and force expand; it presumes caller forced an expand
# thus cells are skipped if:
#   'cellname filepath' is "default" (can mean not expanded yet, or created never saved),
#   'cellname filepath' is <CELLNAME>.mag, indicates failed expand (unbound).
# Note: when filepath gives "default" or <CELLNAME>.mag, the writeable check not meaningful.
#
# How to scratchify all in-memory cells (still subject to internal skipping):
#     scratchWritable {*}[cellname list allcells]
#
# TODO: use a combo of filepath & flags likely can detect created in-mem,
# and could redirect those too scratch dir if named explicitly.
#
# Side-effects:
#   Runs zero or one subprocess, '/bin/mktemp -d' to make a scratch dir.
#   Redirects where newly modified cells would be saved, if they ever are saved.
#   Make's a scratch dir that needs to be cleaned-up.
#   Leaves empty *.mag files in that scratch dir.
#
# Uses/requires proc cellnameExists.
#
# Same scratch-dir is reused if called multiple times, until next -cleanup.
#
# return value: list of cells not processed (skipped) for reasons cited above.
# Non-existent cells are also skipped but not included in the return list.
#
if {![info exists ::scratchWritableDir]} {set ::scratchWritableDir {}}
if {![info exists ::scratchWritableVerb]} {set ::scratchWritableVerb 0}
proc scratchWritable {args} {
    # parse -cleanup option
    set clean [expr {[lindex $args 0] eq {-cleanup}}]
    if {$clean} {
	set args [lrange $args 1 end]
    }

    # If explicit cells given: don't limit to processing just readOnly cells.
    set onlyReadonly [expr {$args == {}}]

    # only if no -cleanup, does empty cell list imply all cells
    set allcell [cellname list allcells]
    if {!$clean && $args == {}} {
	set args $allcell
    }

    # do cleanup
    if {$clean} {
	if {$::scratchWritableDir != {} && [file isdir $::scratchWritableDir]} {
	    set files [glob -dir $::scratchWritableDir -- {*.ext} {*.mag}]
	    lappend files $::scratchWritableDir
	    if {$::scratchWritableVerb} {
		puts "scratchWritable: running, file delete $files"
	    }
	    eval {file delete} $files
	    set ::scratchWritableDir {}
	}
    }

    # Filter out non-existent or unbound cells.
    # Optionally filter already writable cells.
    #
    # Unbounds result from placements of cells that now don't exist:
    # fail to expand.  This proc does not try and force expand; it
    # presumes a forced expand was already done by caller (if caller
    # wished).
    #
    # Referenced/used cells are initially unexpanded, not yet even located
    # located in the search path, 'cellname filepath' returns "default".
    # If expand fails (not found in search path), then 'cellname filepath'
    # returns <CELLNAME>.mag, if expand worked, the directory containing
    # the cell.
    #
    # If cell was 'cellname create' made, but never saved also "default".
    # Such a cell is writable. So filter "default" and <CELLNAME>.mag.
    set skipped {}
    set ercell1 {}
    set docells {}
    foreach cell $args {
	# filter (without recording as skipped) non-existent cells.
	if {![cellnameExists $cell]} { continue }

	# filepath = "default": unexpanded (not loaded from disk),
	# or created in-mem and never saved (is writable already
	# though flags won't say so): skip both.
	# TODO: use a combo of filepath & flags likely can detect created in-mem,
	# and might be able to redirect them too to scratch dir if named explicitly.
	set tmppath [cellname list filepath $cell]
	if {$tmppath eq "default"} {
	    lappend skipped $cell
	    continue
	}

	# flags not meaningful, until expanded or expand attempted.
	# After expand attempt (filepath != "default"), and flags
	# can now be used to determine cell unbound: not available.
	set flags   [cellname list flags    $cell]
	if {[lsearch -exact $flags available] < 0} {
	    lappend ercell1 $cell
	    continue
	}

	if {$onlyReadonly &&
	    [cellname list writeable $cell] eq "writeable"} {
	    lappend skipped $cell
	    continue
	}
	lappend docells $cell
    }

    if {$::scratchWritableVerb} {
	puts "scratchWritable: skipped cells: $skipped"
    }
    
    # don't make a scratch dir if no work to do
    if {$docells == {}} {
	if {$::scratchWritableVerb} {
	    puts "scratchWritable: scratch-directed 0 cells"
	}
	return $skipped
    }

    # make a scratch dir if needed
    if {$::scratchWritableDir == {}} {
	if {[catch {set dir [string trimright [exec /bin/mktemp -d]]} msg]} {
	    error "ERROR: scratchWritable, '/bin/mktemp -d' failed, $msg"
	}
	if {![file isdir $dir] || ![file writable $dir]} {
	    error "ERROR: scratchWritable, mktemp gave $dir, not a writable dir"
	}
	set ::scratchWritableDir $dir
    }

    set ercell2 {}
    set okcell {}
    set madef 0
    foreach cell $docells {
	# Relocate if needed: filepath doesn't already point to the scratch dir).
	# 'cellname list filepath <cellNm>' -> appears to omit .mag extension,
	# but disk-file needs the .mag in the path.
	set trgr [file join $::scratchWritableDir "$cell"]      ;# expected "lookup" path
	set trgw [file join $::scratchWritableDir "$cell.mag"]  ;# true "write" disk path
	set src [cellname list filepath $cell]
	if {[cellname list filepath $cell] ne $trgr && [cellname list filepath $cell] ne $trgw} {

	    # make empty .mag for the cell
	    if {[catch {set outmag [open $trgw w]} msg]} {
		lappend ercell2 $cell
		continue
	    }
	    incr madef
	    close $outmag

	    # relocate cell to new file
	    cellname list filepath $cell $::scratchWritableDir
	}

	# make cell writable
	cellname list writeable $cell true
	lappend okcell $cell
    }

    if {$::scratchWritableVerb} {
	puts "scratchWritable: scratch-directed $madef cells"
    }
    if {$ercell1 != {} || $ercell2 != {}} {
	set pre "ERROR: scratchWritable, "
	set msg {}
	if {$ercell1 != {}} {
	    lappend msg "$pre unbound cell(s): $ercell1"
	}
	if {$ercell2 != {}} {
	    lappend msg "$pre failed to make .mag for cell(s): $ercell2"
	}
	error [join $msg "\n"]
    }
    set skipped
} ;# end proc scratchWritable
}

# without top-level proc around bulk of script, intermediate error statements don't abort script.
proc main {argv} {

# process name-value pair options, if any
set nbrErr 0
set ndx 0
set max [llength $argv]
set extTechOpt {} ;# -T ...
set enumFilel  {} ;# -l ... enum output file
set variant {}    ;# -S ... non-default drc style
set flatten 0
set perfN   0     ;# -P <N> do crude DRC perf. test
set noderef 0     ;# -N disable dereference option of: 'load ... -dereference'

while {$ndx < $max && [string match "-*" [lindex $argv $ndx]]} {
    set opt [lindex $argv $ndx]
    incr ndx
    switch -exact -- $opt {
	-T {
	    if {$ndx == $max} {
		usage "missing tech-file argument for -T option"
		exit 1
	    }
	    set extTechOpt [lindex $argv $ndx]
	    incr ndx
	}
	-S {
	    if {$ndx == $max} {
		usage "missing drcStyle argument for -S option"
		exit 1
	    }
	    set variant [lindex $argv $ndx]
	    incr ndx
	}
	-P {
	    if {$ndx == $max} {
		usage "missing count argument for -P option"
		exit 1
	    }
	    set perfN [lindex $argv $ndx]
	    incr ndx
	}
	-F {
	    set flatten 1
	}
	-N {
	    set noderef 1
	}
	-l {
	    if {$ndx == $max} {
		usage "missing outputFile argument for -l option"
		exit 1
	    }
	    set enumFilel [lindex $argv $ndx]
	    incr ndx
	    if {[catch {set enumOut [open $enumFilel w]} msg]} {
		error "ERROR: ${::Prog}: failed to open-for-write '$enumFilel' threw error, $msg"
	    }
	    puts "${::Prog}: enumerating each error bbox to: $enumFilel"
	}
	default {
	    usage "unknown option: $opt"
	    exit 1
	}
    }
}

if {$ndx == $max} {
    usage "missing magFileName argument, the topcell"
    exit 1
}

# get cmd-line topcell, minus dir-path; and minus extension IFF ext is .mag
set topc [file tail [lindex $argv $ndx]] ; incr ndx
if {[file extension $topc] eq ".mag"} {
    set topc [file rootname $topc]
}
set topcStr $topc

# abort if user supplies extra args.
if {$ndx != $max} {
    usage "extra/unspported arg past magFileName, '[lindex $argv $ndx]'"
    exit 1
}

# load the techfile
if {$extTechOpt != ""} {
    if {![file readable $extTechOpt]} {
	error "ERROR: ${::Prog}: tech-file \"$extTechOpt\" is not readable."
    }

    tech load $extTechOpt
    
    # Verify the cmd-line -T option (if any) is still the current 'tech filename'. If we didn't
    # explicitly 'tech load' ourselves, the .magicrc or magic.setup might 'tech load' something else.
    # The 'file join [pwd] ...' makes relative path absolute, but without resolving
    # all symlinks (which 'file normalize' would do).
    set techf2 [file join [pwd] [tech filename]]
    set techf1 [file join [pwd]     $extTechOpt]
    if {$techf1 != $techf2} {
	error "ERROR: ${::Prog}: failed tech-load \"$techf1\" (tech-filename=\"$techf2\" not a match)"
    }
}

# if mag-cell were passed natively on magic cmd-line, this is too late:
if {$noderef} {
    load $topc
} else {
    load $topc -dereference
}

# error checks: ensure (1st) cmd-line cellname now in-memory, and is now the current cell

set topcells [cellname list top]
# filter (UNNAMED)
set topcells [lsearch -exact -not -all -inline $topcells "(UNNAMED)"]
# puts "cellname-list-top is: $topcells"

# could use [cellname list flags $topc] and ensure non-null result (list with available),
# but if it fails (cell not found), it generates unwanted stdout.
if {[lsearch -exact [cellname list allcells] $topc] < 0} {
    error "ERROR: ${::Prog}: cmd-line topcell \"$topc\" not in magic's list of allcells."
}

if {[lsearch -exact $topcells $topc] < 0} {
    puts "WARNING: ${::Prog}: cmd-line topcell \"$topc\" not in magic's list of topcells: $topcells"
}

# crude way even in batch to determine the "current" cell; perhaps not yet the "Edit" cell
# WARNING, if topcell locked elsewhere or not writable, it can't become the "Edit" cell.
set topcw [cellname list window]
if {$topcw ne $topc} {
    error "ERROR: ${::Prog}: cmd-line topcell, $topc, is not the current cell, 'cellname list window'=$topcw"
}

# for topcell, filepath==default doesn't change by expand,
# indicates unknown cell created in-memory by magic's startup sequence.
if {[cellnameExists         $topc] &&
    [cellname list filepath $topc] eq "default"} {
    puts "Search path for cells is \"[path search]\""
    error "ERROR: ${::Prog}: cmd-line topcell, $topc, auto-created in-memory: not found in cell search path"
}

if {$flatten} {
    # delete (UNNAMED) if any.
    set trg "(UNNAMED)"
    if {[cellnameExists $trg]} {cellname delete $trg}

    # rename top cell to (UNNAMED)
    cellname rename $topc $trg

    # now Edit Cell contents are original top cell, but under name (UNNAMED)
    # flatten Edit-Cell into original top cell name
    puts "${::Prog}: flattening..."
    flatten $topc

    # load and edit new version of top cell. This is from in-memory, just making it current-cell.
    # (So with or without -dereference is expected would have discernable effect by now;
    # and since it's flattened there are no subcell instances either).
    if {$noderef} {
	load $topc
    } else {
	load $topc -dereference
    }

    # crude way even in batch to determine the "current" cell; perhaps not yet the "Edit" cell
    # WARNING, if topcell locked elsewhere or not writable, it can't become the "Edit" cell.
    set topcw [cellname list window]
    if {$topcw ne $topc} {
	error "ERROR: ${::Prog}: assertion failed, post-flatten, $topc, is not the current cell, 'cellname list window'=$topcw"
    }

    # should not be necessary:
    select top cell
    edit

    # crude way even in batch to determine the "current" cell; perhaps not yet the "Edit" cell
    # WARNING, if topcell locked elsewhere or not writable, it can't become the "Edit" cell.
    set topcw [cellname list window]
    if {$topcw ne $topc} {
	error "ERROR: ${::Prog}: assertion-2 failed, post-flatten, $topc, is not the current cell, 'cellname list window'=$topcw"
    }
}

# todo: Need a check for non-existent topcell (though magic reported not-found and auto-created it).
# todo: We should locate fullpath to topcell on disk to record this in the log.
#
# WARNING, magic junkCell, or magic junkDir/junkCell (passing paths to cells that don't exist),
# generate startup error messages (could not open cell), but magic creates the new cell in memory.
# No simple way to detect this after the fact. Can walk the cell search path to verify it's on disk.
# For the non-existent cell, magic also discards the dirpath from the cmd-line arg.
# If it did exist at that path, magic opens it successfully, despite that dir not in search path.
# A proper check for implicit create of non-existent cell should account for this effect too.

# write a line with timestamp and all arguments to stdout (log)
# (magic renames the TCL clock command)
set clockp clock
if {[info command $clockp] == {} && [info command orig_clock] != {}} {
    set clockp orig_clock
}
set nowSec [$clockp seconds]
set timestamp [$clockp format $nowSec -format "%Y-%m-%d.%T.%Z"]
# Show quoted logged argv here so it's machine readable for replay purposes.
puts "${::Prog}: timestamp: $timestamp, arguments: $::env(_M0)"

puts "${::Prog}: running drc on topcell: $topcStr"
puts "${::Prog}: tech-name: [tech name] -version: [tech version] -filename: [tech filename] -lambda [tech lambda]"

# log the cell search path for this run. Emulates format output by plain "path" (but which prints more than one the cell search path).
puts "Search path for cells is \"[path search]\""

set res {}
if {$variant != {}} {
    if {[catch {set res [drc list style $variant]} msg]} {
	puts "ERROR: ${::Prog}: but CONTINUING, 'drc style $variant' threw error, $msg"
    }
} else {
    if {[catch {set res [drc list style]} msg]} {
	puts "ERROR: ${::Prog}: but CONTINUING, 'drc list style' threw error, $msg"
    }
}
if {$res != {}} {
    puts "drc style reports:\n$res"
}

# just Manhattan is default, turn on euclidean, and log new mode
drc euclidean on
drc euclidean

# 1st "select top cell": without it drc-list-count is blank, and error count reduced.
# May be unnecessary in some cases.
# WARNING: if topcell locked by another process, default box is NOT set to full top cell without this (as of 8.1.70 or earlier)
select top cell
# expand cell cells: scratchify step requires this up front else can't force all cells writable.
expand

# The expand triggered load of all subcells. Till then allcells may be incomplete.
set allcells [cellname list allcells]
# filter (UNNAMED)
set allcells [lsearch -exact -not -all -inline $allcells "(UNNAMED)"]
set nbrAllCells [llength $allcells]
# puts "DEBUG: cellname-list-allcells are: $allcells"

# TODO: do explicit separate unbound check here (don't rely on scratchWritable for this)

# make allcells writable. Can error out:
# if are unbounds, or couldn't make scratch dir or .mag files.
set scratch [expr {!$flatten}]
if {$scratch && [catch {scratchWritable} msg]} {
    puts stderr "ERROR: ${::Prog}: aborting at scratchWritable due error(s):"
    error $msg
}

# Erase all preexisting *.drtcl first. Else when cell transitions from
# dirty in previous run (leaving *.drtcl), to clean, the old *.drtcl
# remains.
# TODO: only delete *.drtcl of cells in 'cellname list allcells'?
# TODO: move this up, before scratchWritable?
set files [glob -nocomplain -types {f} -- ./*.drtcl]
if {$files != {}} {
    # TODO: detect/report failure details better here?
    puts "${::Prog}: deleting preexisting *.drtcl"
    set msg {}
    set delfail [catch {eval {file delete} $files} msg]
    set files [glob -nocomplain -types {f} -- ./*.drtcl]
    if {$delfail || $files != {}} {
	puts "ERROR: ${::Prog}: failed to clean old ./*.drtcl files. $msg"
	incr nbrErr
    }
}

edit ;# Fails if topcell not writable, should not be not needed post scratchWritable

set outScale [cif scale out]

# "select top cell" and box [view bbox] should be equivalent in
# placing a box around whole cell extent.
# The box cmd ALSO prints lambda and micron user-friendly box data,
# but it prints microns with not enough resolution,
# (and no option to disable that flawed print out).
#
# todo: emulate box output in full, except for higher resolution,
# here we only scale/print the overall bbox in microns.
# select top cell       ;# paranoid, reset the box to data extents post-expand
# set bbox [view bbox]
# set bbs {}
# foreach oord $bbox {
#     lappend bbs [format "%.3f" [expr {$outScale * $oord}]]
# }
# puts "outScale: $outScale, view-bbox: $bbox"
# puts "Root cell box2: ([lindex $bbs 0]  [lindex $bbs 1]), ([lindex $bbs 2]  [lindex $bbs 3])"

# shouldn't need:
# drc on

# Want to use 'drc list count' to tell us which cells have errors, so we can
# run 'drc listall why' on just those cells to enumerate details (which reruns
# drc again unfortunately).

# For accurate DRC (as of 8.1.70), specifically 'drc list count', need:
# all-writable cells, then run: 'drc check' & 'drc catchup'.
# Now we have all writable cells.
set timeRepeat 1
if {$perfN > 0} {
    set timeRepeat $perfN
}
set timeres [time {
    set drcCheckTime1 [time {drc check}]
    set drcCheckTime2 [time {drc catchup}] } $timeRepeat]

if {$perfN > 0} {
    puts "perf: ${perfN}X 'drc check','drc catchup': $timeres"
    puts "perf: last 'drc check' time: $drcCheckTime1"
    puts "perf: last 'drc catchup' time: $drcCheckTime2"
    drc statistics
    drc rulestats
}

# todo: this 2nd select was in GDS version, test if needed in mag version:
# 2nd select top cell needed else error count may be reduced (why? bbox does not change due to DRC)
select top cell
set outScale [cif scale out]
set bbox [view bbox]
set bbs {}
foreach oord $bbox {
    lappend bbs [format "%.3f" [expr {$outScale * $oord}]]
}
puts "outScale(ostyle=[cif list ostyle]): $outScale, view-bbox: $bbox"
puts "Root cell box: ([lindex $bbs 0]  [lindex $bbs 1]), ([lindex $bbs 2]  [lindex $bbs 3])"
# print several native bbox representations:
box

# listall vs list appear same as of 8.1.70 or earlier.
# warning: celllist order is not stable, not repeatable; run to run on same data.
# puts "DEBUG: (drc listall count total) is $drcListCountTot"
set celllist [drc listall count]
set celllist [lsearch -not -all -inline -index 0 -exact $celllist "(UNNAMED)"]
# puts "DEBUG: (drc listall count) is [drc listall count]"
set drcListCountTot [drc list count total]
set nbrErrCells [llength $celllist]

# TODO: major problem: 'drc listall why' repeated an every cell, will do subcells
# multiple times, as many times as their depth in the hier.

# canonicalize order of celllist, move topc to last (if present whatsoever).
# force our own artificial entry for topc (zero errors) if not present (was clean)
# puts "DEBUG: celllist before: $celllist"
set topcPair [lsearch           -inline -index 0 -exact $celllist $topc]
set celllist [lsearch -not -all -inline -index 0 -exact $celllist $topc]
set celllist [lsort -index 0 -dictionary $celllist]
if {$topcPair == {}} {
    # puts "DEBUG: $topc clean, forcing celllist entry for it"
    set topcPair [list $topc 0]
}
lappend celllist $topcPair
# puts "DEBUG: celllist after: $celllist"
# puts "DEBUG: adjusted celllist(drc list count) is $celllist"

# loop over celllist
set doFeedback 1 ;# TODO: add cmd-line option to control this

# collect 'dry listall why' for the cells in 'cell list count' with non-zero errors
# If 'drc listall why' does report zero (shouldn't since we're only processing cells
# with non-zero counts), it unavoidably writes to console a No drc errors found message.
# We don't want such polluting our list of per-cell pareto's, so don't risk running
# drc why in-line, in-between per-cell paretos.
array set cell2why [list $topc {}] ;# default at least empty topcell why list
foreach pair $celllist {
    if {[lindex $pair 1] < 1} {continue} ;# only happens for topcell if topcell clean
    set acell [lindex $pair 0]

    # TODO: magic needs a useful error checkable load command.
    # The 'load' writes errors to console/stdout, but never throws an error,
    # nor gives a useful return value. i.e. These catch never catch.
    if {$noderef} {
	if {[catch {set res [load $acell]} msg]} {
	    puts "ERROR: ${::Prog}: 'load $acell' threw error, $msg"
	    exit 1
	}
    } else {
	if {[catch {set res [load $acell -dereference]} msg]} {
	    puts "ERROR: ${::Prog}: 'load $acell -dereference' threw error, $msg"
	    exit 1
	}
    }
    select top cell ;# paranoid, that without it, drc's are reduced

    # optionally do crude DRC perf. analysis here. Only for top-cell, only if -P <N> option given.
    set timeRepeat 1
    if {$perfN > 0 && $topc eq $acell} {
	set timeRepeat $perfN
    }
    set timeres [time {set cell2why($acell) [drc listall why]} $timeRepeat]
    if {$perfN > 0 && $topc eq $acell} {
	puts "perf: ${::Prog}: for '$acell', ${perfN}X 'drc listall why': $timeres"
    }
}

# done with all magic-specifics here. Shouldn't need scratch dir any longer.
# If this prints something (generally does), don't want it after the pareto table.

# clean/remove the tmp scratch dir and contents
# TODO: all fatal errors need to call a cleanup proc that includes this before abort
if {$scratch && [catch {scratchWritable -cleanup} msg]} {
    puts "ERROR: ${::Prog}: 'scratchWritable -cleanup' threw error, $msg"
    incr nbrErr
}

set gtotal 0
set gcells 0
foreach pair $celllist {
    puts ""
    set acell [lindex $pair 0]
    if {![info exists cell2why($acell)]} {
	puts "ERROR: ${::Prog}: cell: $acell, assertion failed, no drc-why list for 'drc list count' pair: $pair"
	# exit 1
	continue
    }
    set whys $cell2why($acell)

    # enumerate errors under box, plain "drc why" only reports unique types, no quantities
    # as-yet-undocumented "drc listall why" gives: {errStr1 {errBox1 ...} errStr2 {errBox1 ...} ... }
    set pareto {}
    set total 0
    set enumTotal 0
    set types 0
    set typeDup 0
    set dups 0

    set fbOut {}
    # file path for feedback, keep in CWD
    if {$doFeedback && $fbOut == {}} {
	set fbOut "./$acell.drtcl"
	if {![file writable $fbOut] &&
	    ([file exists $fbOut] || ![file writable [file dir $fbOut]])} {
	    puts stderr "ERROR: ${::Prog}: feedback output not writable, $fbOut"
	    incr nbrErr
	    set fbOut {}
	} elseif {[catch {set outfb [open $fbOut w]} msg]} {
	    puts stderr "ERROR: ${::Prog}: failed to truncate previous feedback output, $fbOut : $msg"
	    incr nbrErr
	    set fbOut {}
	}
    }
    foreach {str boxes} $whys {
	# sort errors
	set boxes [lsort -dictionary $boxes]

	# for our pareto, gather data
	set this [llength $boxes]
	incr total $this
	incr types
	lappend pareto [list $this $str]

	# for enumOut, emulate formatting of $CAD_ROOT/magic/tcl/drc.tcl, which is
	# not tk pure: fails with complaint about winfo
	# note: we walk these errors also in order to count/report stats on duplicates, even if not outputing enumerations
	if {[info exists enumOut]} {
	    if {$types == 1} {
		puts $enumOut "[join $pair]\n----------------------------------------"
	    }
	    puts $enumOut "${str}\n----------------------------------------"
	}
	set lastq {}
	set thisDup 0
	foreach quad $boxes {
	    set quadUM {}
	    foreach coord $quad {
		set valum [expr {$coord * $outScale}]
		set valumf [format "%.3f" $valum]
		lappend quadUM "${valumf}um"
	    }
	    set dup [expr {$quad == $lastq}]
	    incr thisDup $dup
	    set line $quadUM
	    if {[info exists enumOut]} {
		if {$dup} {
		    puts $enumOut "[join $line] #dup"
		} else {
		    puts $enumOut [join $line]
		}
	    }
	    if {$fbOut != {}} {
		set line [join $quadUM]
		regsub -all -- "(\[\[\"\$\\\\])" $str {\\\1} strdq
		puts $outfb "[concat box $line]"                nonewline
		puts $outfb " ; feedback add \"$strdq\" medium" nonewline
		if {$dup} {
		    puts $outfb " ;#dup"
		} else {
		    puts $outfb ""
		}
	    }

	    incr enumTotal
	    set lastq $quad
	}
	if {$thisDup} {
	    incr typeDup
	    incr dups $thisDup
	}
	if {[info exists enumOut]} {
	    puts $enumOut "----------------------------------------\n"
	}
    }

    if {$fbOut != {}} {
	close $outfb
	set outfb {}
    }

    set pareto [lsort -integer -decreasing -index 0 $pareto]
    if {$total > 0} {
	puts "--- #err|description, table for cell: $acell"
    }
    foreach pair $pareto {
	puts "[format {%8d} [lindex $pair 0]] [lindex $pair 1]"
    }
    if {$typeDup} {
	puts "[format {%8d} $dups] total duplicate error(s) among $typeDup error type(s), cell: $acell"
    }
    puts "[format {%8d} $total] total error(s) among $types error type(s), cell: $acell"
    # add to grand-totals
    incr gcells
    incr gtotal $total

    # always compare the total from the enum to the pareto as error check
    if {$total != $enumTotal} {
	puts "ERROR: ${::Prog}: cell: $acell, assertion failed, pareto vs enum count mismatch: $total != $enumTotal"
	incr nbrErr
    }
}

# TODO: in the summary echo also techfile-full-path and drc-style name?
# grand totals
puts "[format {%8d} $nbrErrCells] of $nbrAllCells cell(s) report error(s)"
puts "[format {%8d} $gtotal] grand-total error(s) across $gcells cell(s)"

# wish to compare the drc-list-count-total to the pareto total.
# Per te 2014-08-27 : it is not an error.
# if {$total != $drcListCountTot} {
#   puts "info: ${::Prog}: drc-list-count-total vs drc-listall-why mismatch {drc list count total} gave $drcListCountTot, but {drc listall why} gave $total"
# }

if {[info exists enumOut]} {
    close $enumOut
}

# set celllist4 [drc list count]
# puts "DEBUG: drc list count0: $celllist0"
# puts "DEBUG: drc list count1: $celllist1"
# puts "DEBUG: drc list count2: $celllist2"
# puts "DEBUG: drc list count3: $celllist3"
# puts "DEBUG: native (drc list count) is $celllistn"
# puts "DEBUG: drc list count4: $celllist4"

# todo: implement super-pareto, ranked table of SUM of all DRC errs/counts from ALL cells.
# (It still would not reflect as-if-flat hierarchical expansion due to repetition of instances).

set nbrErr
}

# non-zero exit-status on errors, either if thrown by main, or counted and returned by main
set nbrErr 0
if {[catch {set nbrErr [main $argv]} msg]} {
    puts stderr $msg
    set nbrErr 1
} elseif {$nbrErr > 0} {
    puts "ERROR: ${::Prog}: script terminated with errors reported above."
}
exit $nbrErr

# for emacs syntax-mode:
# Local Variables:
# mode:tcl
# End:
